package semant;

import java.util.Iterator;

import util.ClassTreeNode;
import util.ErrorHandler;
import util.SymbolTable;
import visitor.Visitor;
import ast.*;

public class TypeCheckerVisitor extends SemanticVisitor {

	ErrorHandler errorhandler;
	ClassTreeNode ctnode;
	SymbolTable vt; //Variable Table
	SymbolTable mt; //Method Table
	
	Method currentMethod;
	int classScopeLevel;
	boolean validStmt;
	boolean insideLoop;
	boolean foundReturn;
	boolean privateLength;
	
	public TypeCheckerVisitor(ClassTreeNode node,
			ErrorHandler errorHandler)
	{
		this.ctnode = node;
		this.vt = node.getVarSymbolTable();
		this.mt = node.getMethodSymbolTable();
		this.errorhandler = errorHandler;
		currentMethod = null;
		validStmt = false;
		insideLoop = false;
		foundReturn = false;
		privateLength = false;
		classScopeLevel = 1;
	}
	
	
	/** Visit a class node
     * @param node the class node
     * @return result of the visit 
     * */
   public Object visit(Class_ node) {
			node.getMemberList().accept(this);
		return null;
   }

   /** Visit a list node of members
     * @param node the member list node
     * @return result of the visit 
     * */
   public Object visit(MemberList node) {
		for (Iterator it = (Iterator) node.getIterator(); it.hasNext();)
		    ((Member)it.next()).accept(this);
		return null;
   }

   /** Visit a field node
     * @param node the field node
     * @return result of the visit 
     * */
   public Object visit(Field node) {
	   String type ;
	   if(node.getInit() == null)
		   type = null;
	   else
		   type = (String)node.getInit().accept(this);
	   if(type != null) {
			if(!typeConformChecker(node.getType(), type)) {
				if(type.equals("void")) {
					errorhandler.register(errorhandler.SEMANT_ERROR, 
		                    ctnode.getASTNode().getFilename(), 
		                    node.getLineNum(),
							"expression type '" + type + "' of field '" + 
							node.getName() + "' cannot be void");
				} else {
					if(isPrimitive(node.getType()) || isPrimitive(type)) {
		    			errorhandler.register(errorhandler.SEMANT_ERROR, 
			                    ctnode.getASTNode().getFilename(), 
			                    node.getLineNum(),
								"expression type '" + type + "' of declaration '" +
								node.getName() + "' does not match declared type '" +
								node.getType() + "'");
		    		} else {
		    			errorhandler.register(errorhandler.SEMANT_ERROR, 
			                    ctnode.getASTNode().getFilename(), 
			                    node.getLineNum(),
								"expression type '" + type + "' of declaration '" +
								node.getName() + "' does not conform declared type '" +
								node.getType() + "'");
		    		}
				}
			}
	   }
	   if(!isPrimitive(node.getType()) && !isObject(node.getType())) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
					"type '" + node.getType() + "' of field '" + 
					node.getName() +"' is undefined");
		}
		return null; 
   }

   /** Visit a method node
     * @param node the method node
     * @return result of the visit 
     * */
   public Object visit(Method node) {
	   //Check some other errors
	   	//Get Formal List
	    vt.enterScope();
	    classScopeLevel++;
	    
		currentMethod = node;
		String methodHeader = (String)node.getFormalList().accept(this);
		String[] mhSplit = methodHeader.split("->");
		String[] argList;
		if(mhSplit.length == 1) {
    		argList = new String[1];
    		argList[0]="";
    	} else {
    		argList = mhSplit[1].split("*");
    	}
	    ClassTreeNode cnode = ctnode.getParent();
	    String m;
	    
	    while(cnode!=null) {
    		String parentMethodHeader = (String)cnode.getMethodSymbolTable().peek(node.getName());
    		
    		if(parentMethodHeader != null) {
    			//Compare inherited method arguments and return type
		    	String [] pmhSplit = parentMethodHeader.split("->");
		    	String [] pargList;
		    	if(pmhSplit.length == 1) {
		    		pargList = new String[1];
		    		pargList[0]="";
		    	} else {
		    		pargList = pmhSplit[1].split("*");
		    	}
		    	if(!pmhSplit[0].equals(node.getReturnType()))
		    		errorhandler.register(errorhandler.SEMANT_ERROR, 
		                    ctnode.getASTNode().getFilename(), 
		                    node.getLineNum(),
							"overriding method '" + node.getName() +
							"' has return type '" + pmhSplit[0] +
							"', which differs from the inherited "+
							"method's return type '" +
							node.getReturnType() + "'");
		    	if(argList.length != pargList.length) {
		    		errorhandler.register(errorhandler.SEMANT_ERROR, 
		                    ctnode.getASTNode().getFilename(), 
		                    node.getLineNum(),
							"overriding method '" + node.getName() +
							"' has " + argList.length +
							" formals, which differs from the inherited "+
							"method (" + pargList.length + ")");
		    	}
		    	int i = 0;
		    	while(i < argList.length && i < pargList.length){
		    		if(!argList[i].equals(pargList[i]))	{
		    			errorhandler.register(errorhandler.SEMANT_ERROR, 
			                    ctnode.getASTNode().getFilename(), 
			                    node.getLineNum(),
								"overriding method '" + node.getName() +
								"' has formal type '" + argList[i] +
								"' for formal " + i + ", which differs " +
								"from the inherited method's formal type '" +
								pargList[i] + "'");
		    		}
		    		i++;
		    	}
		    	break;
    		}
    		cnode = cnode.getParent();
	    }
	   
	    foundReturn = false;
		node.getStmtList().accept(this);
		if(!foundReturn) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
					"declared return type of method '" +
					node.getName() + "' is '" + node.getReturnType() +
					"' but method body is not returning any expression");
		}
		
		currentMethod = null;
		vt.exitScope();
		classScopeLevel--;
		return null; 
   }
   
    /** Visit a list node of formals
      * @param node the formal list node
      * @return result of the visit 
      * */
    public Object visit(FormalList node) { 
    	String s = "";
       	for (Iterator it = (Iterator) node.getIterator(); it.hasNext();)
    		    s += ((String)((Formal)it.next()).accept(this)) + "*";
       	if(s.length() != 0)
       		s = s.substring(0,s.length()-1);
       	return s;
    }

    /** Visit a formal node
      * @param node the formal node
      * @return result of the visit 
      * */
    public Object visit(Formal node) {
		if(vt.peek(node.getName()) != null) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
					"formal '" + node.getName() + "'" +
					" is multiply defined");
		} else if(reservedNameChecker(node.getName())){
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
					"formals cannot be named '" + 
					node.getName() + "'");
		} else if(!isPrimitive(node.getType()) && !isObject(node.getType())) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
					"type '" + node.getType() + "' of formal '" + 
					node.getName() +"' is undefined");
		}
		vt.add(node.getName(),node.getType());
		return node.getType();
    }

    /** Visit a list node of statements
      * @param node the statement list node
      * @return result of the visit 
      * */
    public Object visit(StmtList node) {
		for (Iterator it = node.getIterator(); it.hasNext(); ) {
			validStmt = false;
			Stmt stmt = (Stmt)it.next();
		    stmt.accept(this);
		    if(!validStmt) {
		    	errorhandler.register(errorhandler.SEMANT_ERROR, 
	                    ctnode.getASTNode().getFilename(), 
	                    stmt.getLineNum(),
						"not a statement");
		    }
		}
		return null;
    }

    /** Visit a declaration statement node
      * @param node the declaration statement node
      * @return result of the visit 
      * */
    public Object visit(DeclStmt node) {
    	validStmt = true;
    	if(vt.peek(node.getName()) != null) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
					"variable '" + node.getName() + "'" +
					" is already defined in method " + 
					currentMethod.getName());
		} else if(reservedNameChecker(node.getName())){
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
					"variables cannot be named '" + 
					node.getName() + "'");
		}
    	String type = (String)node.getInit().accept(this);
    	if(!typeConformChecker(node.getType(), type)) {
    		if(isPrimitive(node.getType()) || isPrimitive(type)) {
    			errorhandler.register(errorhandler.SEMANT_ERROR, 
	                    ctnode.getASTNode().getFilename(), 
	                    node.getLineNum(),
						"expression type '" + type + "' of declaration '" +
						node.getName() + "' does not match declared type '" +
						node.getType() + "'");
    		} else {
    			errorhandler.register(errorhandler.SEMANT_ERROR, 
	                    ctnode.getASTNode().getFilename(), 
	                    node.getLineNum(),
						"expression type '" + type + "' of declaration '" +
						node.getName() + "' does not conform declared type '" +
						node.getType() + "'");
    		}
    	}
		vt.add(node.getName(), node.getType());
		return null;
    }

    /** Visit an expression statement node
      * @param node the expression statement node
      * @return result of the visit 
      * */
    public Object visit(ExprStmt node) { 
		node.getExpr().accept(this);
		return null; 
    }

    /** Visit an if statement node
      * @param node the if statement node
      * @return result of the visit 
      * */
    public Object visit(IfStmt node) {
    	validStmt = true;
    	String type = (String)node.getPredExpr().accept(this);
	    if(!type.equals("boolean")) {
		    errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
					"predicate in if-statement" +
					" does not have type boolean");
	    }
		vt.enterScope();
		classScopeLevel++;
		node.getThenStmt().accept(this);
		vt.exitScope();
		vt.enterScope();
		node.getElseStmt().accept(this);
		vt.exitScope();
		classScopeLevel--;
	return null; 
    }

    /** Visit a while statement node
      * @param node the while statement node
      * @return result of the visit 
      * */
    public Object visit(WhileStmt node) { 
    	validStmt = true;
    	insideLoop=true;
    	String type = (String)node.getPredExpr().accept(this);
	    if(type.equals("boolean")) {
		    errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
					"predicate in while-statement" +
					"does not have type boolean");
	    }
		vt.enterScope();
		classScopeLevel++;
		node.getBodyStmt().accept(this);
		vt.exitScope();
		classScopeLevel--;
		insideLoop=false;
		return null; 
    }

    /** Visit a for statement node
      * @param node the for statement node
      * @return result of the visit 
      * */
    public Object visit(ForStmt node) {
    	validStmt = true;
    	insideLoop = true;
		if (node.getInitExpr() != null)
		    node.getInitExpr().accept(this);
		if (node.getPredExpr() != null) {
		    String type = (String)node.getPredExpr().accept(this);
		    if(!type.equals("boolean")) {
			    errorhandler.register(errorhandler.SEMANT_ERROR, 
	                    ctnode.getASTNode().getFilename(), 
	                    node.getLineNum(),
						"predicate in for-statement" +
						" does not have type boolean");
		    }
		}
		if (node.getUpdateExpr() != null)
		    node.getUpdateExpr().accept(this);
		node.getBodyStmt().accept(this);
		insideLoop=false;
	return null; 
    }

    /** Visit a block statement node
      * @param node the block statement node
      * @return result of the visit 
      * */
    public Object visit(BlockStmt node) {
    	validStmt = true;
		vt.enterScope();
		classScopeLevel++;
		node.getStmtList().accept(this);
		vt.exitScope();
		classScopeLevel--;
		return null; 
    }

    /*	Visit a break statement node
     *
     * */
    public Object visit(BreakStmt node) {
    	validStmt = true;
    	if(!insideLoop) {
    		errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
					"break statement is not inside a loop");
    	}
    	return null;
    }
     /** Visit a return statement node
      * @param node the return statement node
      * @return result of the visit 
      * */
    public Object visit(ReturnStmt node) { 
    	validStmt = true;
    	foundReturn =true;
    	String returnType;
    	if(node.getExpr() == null) {
    		returnType = null;
    	} else {
    	  returnType = (String) node.getExpr().accept(this);
    	}
    	
    	if(returnType == null) {
    		if(!currentMethod.getReturnType().equals("void"))
    		{
    			errorhandler.register(errorhandler.SEMANT_ERROR, 
	                    ctnode.getASTNode().getFilename(), 
	                    node.getLineNum(),
	                    "return type 'void' is not compatible"+
	                    " with declared return type '"+ 
	                    currentMethod.getReturnType() +"' in method '"+ 
	                    currentMethod.getName() +"'");
    		}
    	} else if(returnType.equals("void")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
					"cannot return an expression of " + 
					"type 'void' from a method");
		} else if(!typeConformChecker(currentMethod.getReturnType(), returnType)) {
				errorhandler.register(errorhandler.SEMANT_ERROR, 
	                    ctnode.getASTNode().getFilename(), 
	                    node.getLineNum(),
	                    "return type '"+ returnType +"' is not compatible"+
	                    " with declared return type '"+ 
	                    currentMethod.getReturnType() +"' in method '"+ 
	                    currentMethod.getName() +"'");
		} 
		return null;
    }

    /** Visit a list node of expressions
      * @param node the expression list node
      * @return result of the visit 
      * */
    public Object visit(ExprList node) {
    	String s= "";
		for (Iterator it = node.getIterator(); it.hasNext(); )
		    s += ((String)((Expr)it.next()).accept(this)) + "*";
		if(s.length() != 0)
			s.substring(0,s.length()-1);
		return s;
    }

    /** Visit a dispatch expression node
      * @param node the dispatch expression node
      * @return result of the visit 
      * */
    public Object visit(DispatchExpr node) {
    	validStmt = true;
    	String header = null;
    	String type = (String)node.getRefExpr().accept(this);
    	if(isPrimitive(type) || type.equals("void")) {
    		errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
					"can't dispatch on a primitive or void type");
    		header = "void"; //or should it be reference type?
    	} else {
    		header = (String)getMethod(ctnode.getName(), node.getMethodName());
    		if(header == null) {
    			errorhandler.register(errorhandler.SEMANT_ERROR, 
	                    ctnode.getASTNode().getFilename(), 
	                    node.getLineNum(),
						"dispatch to unknown method '" + 
						node.getMethodName() + "'");
    		} else {
    			String[] hSplit = header.split("->");
    			header = hSplit[0]; //update header for return
    			String[] hArgs;
    			
    			if(hSplit.length == 1) {
    				hArgs = new String[1];
    				hArgs[0]="";
    			} else {
    				hArgs= hSplit[1].split("*");
    			}
    			 
    			String expr = (String)node.getActualList().accept(this);
    			String [] exprList;
    			
    			if(expr.equals("")) {
    				exprList = new String[1];
    				exprList[0] = "";
    			} else {
    				exprList = expr.split("*");
    			}
    			
    			if(hArgs.length != exprList.length) {
    				errorhandler.register(errorhandler.SEMANT_ERROR, 
    	                    ctnode.getASTNode().getFilename(), 
    	                    node.getLineNum(),
    						"number of actual parameters (" + 
    						exprList.length +
    						"differs from number of formal parameters (" 
    						+ hArgs.length + "in dispatch method '" +
    						node.getMethodName() + "'");
    			}
    			int i = 0;
				while(i < hArgs.length && i < exprList.length)
				{
					if(exprList[i].equals("void")) {
						errorhandler.register(errorhandler.SEMANT_ERROR, 
	    	                    ctnode.getASTNode().getFilename(), 
	    	                    node.getLineNum(),
	    						"actual parameter " + (i+1) + 
	    						" in the call to method " + 
	    						node.getMethodName() + " is void and cannot be " +
	    						"used within an expression");
					}
					if(!hArgs[i].equals(exprList[i])) {
						errorhandler.register(errorhandler.SEMANT_ERROR, 
	    	                    ctnode.getASTNode().getFilename(), 
	    	                    node.getLineNum(),
	    						"actual parameter " + (i+1) + 
	    						" with type '" + exprList[i] + 
	    						"' does not match formal parameter " +
	    						(i+1) + " with declared type '" + 
	    						hArgs[i] + "' in dispatch to method '" 
	    						+ node.getMethodName()+ "'");
					}
					i++;
				}
    			
    		}
    	}
    	//this might return null;
    	return (header == null) ? "Object" : header; 
    }

    /** Visit a new expression node
      * @param node the new expression node
      * @return result of the visit 
      * */
    public Object visit(NewExpr node) { 
    	validStmt = true;
    	boolean obj=false;
    	if(!isObject(node.getType())){
    		//not an object. does not exist.
    		if(isPrimitive(node.getType())){
    			// send out error. prim type.
    			errorhandler.register(errorhandler.SEMANT_ERROR, 
	                    ctnode.getASTNode().getFilename(), 
	                    node.getLineNum(),
						"type '"+ node.getType() +"' of new construction "+
						"is primitive and cannot be constructed");
    		}else{
    			//an object not declared.
    			errorhandler.register(errorhandler.SEMANT_ERROR, 
	                    ctnode.getASTNode().getFilename(), 
	                    node.getLineNum(),
	                    "type '"+ node.getType() +"' of new construction "+
						"is undefined");
    		}
    		
    	}else{
    		obj= true;
    	}
    	
    	return (obj ? node.getType() : "Object"); 
    }

    /** Visit a new array expression node
      * @param node the new array expression node
      * @return result of the visit 
      * */
    public Object visit(NewArrayExpr node) { 
		String type = (String) node.getSize().accept(this);
		boolean isArray = true;
		if(!isObject(node.getType()) && !isPrimitive(node.getType())) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "type '"+ node.getType() +"' of new construction "+
					"is undefined");
			isArray=false;
		} 
		if(!type.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "size in the array construction has type '" + type +
                    "rather than int");
		}
		//Object[] return might create problems
		return (isArray) ? node.getType() + "[]" : "Object[]"; 
    }

    /** Visit an instanceof expression node
      * @param node the instanceof expression node
      * @return result of the visit 
      * */
    public Object visit(InstanceofExpr node) { 
		String type = (String) node.getExpr().accept(this);
		if(isPrimitive(type)) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the instanceof lefthand expression has type '" +
                    type + "', which is primitive and not an object type");
		} else if(!isObject(type)) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the instanceof righthand type '" +
                    type + "' is undefined");
		}
		if(isPrimitive(node.getType())) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the instanceof righthand type '" +
                    type + "' is primitive and not an object type");
		}
		
		return "boolean"; 
    }

    /** Visit a cast expression node
      * @param node the cast expression node
      * @return result of the visit 
      * */
    public Object visit(CastExpr node) { 
    	String type = node.getType();
		String exprType = (String) node.getExpr().accept(this);
		boolean isObject = true;
		if(isPrimitive(type)) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the target type '" +
                    type + "' is primitive and not an object type");
			isObject=false;
		} else if(type.contains("[]") && 
				!(isObject(type.replace("[]","")) || isPrimitive(type.replace("[]","")))) {
			errorhandler.register(errorhandler.SEMANT_ERROR,
					ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
					"the base type in the target array type '" +
					type.replace("[]","") + "' is undefined");
			type = "Object[]";
    	} else if(!isObject(type) && !isObject(type.replace("[]",""))) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the target type '" +
                    type + "' is undefined");
			isObject=false;
		} 
		if(isPrimitive(exprType)) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "expression in cast has type '" +
                    type + "', which is primitive and can't be casted");
		}
		
		if(!isPrimitive(type) && !isPrimitive(exprType) && 
				(isObject(type) || isObject(type.replace("[]","")) || 
						isPrimitive(type.replace("[]",""))) &&
				(isObject(exprType) || isObject(exprType.replace("[]","")) || 
						isPrimitive(type.replace("[]",""))) &&
				(!typeConformChecker(type,exprType) && 
						!typeConformChecker(exprType,type))) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "inconvertible types ('" + exprType +
                    "'=>'" + type + "')");
		}
			
		return (isObject) ? type : "Object"; 
    }

    /** Visit an assignment expression node
      * @param node the assignment expression node
      * @return result of the visit 
      * */
    public Object visit(AssignExpr node) {
    	validStmt = true;
		
    	String ref = (String) node.getRefName();
    	String left = node.getName();
		String rightType = (String) node.getExpr().accept(this);
		String type = null;
		
		if(ref == null) {
			String leftType = (String) vt.lookup(left);
			if(!leftType.equals(rightType)) {
				if(isPrimitive(leftType) || isPrimitive(rightType)) {
					errorhandler.register(errorhandler.SEMANT_ERROR, 
		                    ctnode.getASTNode().getFilename(), 
		                    node.getLineNum(),
		                    "the lefthand type '" + leftType + 
		                    "' and righthand type '" +
		                    rightType + "' are not compatible");
				} else if(!typeConformChecker(leftType, rightType)) {
					errorhandler.register(errorhandler.SEMANT_ERROR, 
		                    ctnode.getASTNode().getFilename(), 
		                    node.getLineNum(),
		                    "the righthand type '" + rightType + 
		                    "' does not conform to the lefthand type '" +
		                    leftType + "' in assignment");
				}
			}
			type = leftType;
		} else if(node.getRefName().equals("this")) {
			type = (String) findVariable(node.getName(), ctnode);
		} else if(node.getRefName().equals("super")) {
			type = (String) findVariable(node.getName(), ctnode.getParent());
		} else if(node.getName().equals("length")) {
			type = (String) findVariable(node.getRefName(), ctnode);
			if(type != null && type.contains("[]"))
				errorhandler.register(errorhandler.SEMANT_ERROR,
						ctnode.getASTNode().getFilename(), 
	                    node.getLineNum(),
						"length field in array '" + node.getRefName() +
						"': cannot be modified");
		}
		
		if(type == null) {
			errorhandler.register(errorhandler.SEMANT_ERROR,
					ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
					"variable '" + node.getName() +
					"' in assignment is undeclared");
		}
		
		return (type == null) ? "Object" : type; 
    }

    /** Visit an array assignment expression node
      * @param node the array assignment expression node
      * @return result of the visit 
      * */
    public Object visit(ArrayAssignExpr node) {
    	validStmt = true;
    	
    	String ref = (String) node.getRefName();
		String rightType = (String) node.getExpr().accept(this);
		String type = null;
		
		if(ref == null) {
			String leftType = (String) vt.lookup(node.getName());
			if(leftType != null) {
				leftType = leftType.replace("[]","");
				if(!leftType.equals(rightType)) {
					if(isPrimitive(leftType) || isPrimitive(rightType)) {
						errorhandler.register(errorhandler.SEMANT_ERROR, 
			                    ctnode.getASTNode().getFilename(), 
			                    node.getLineNum(),
			                    "the lefthand type '" + leftType + 
			                    "' and righthand type '" +
			                    rightType + "' are not compatible");
					} else if(!typeConformChecker(leftType, rightType)) {
						errorhandler.register(errorhandler.SEMANT_ERROR, 
			                    ctnode.getASTNode().getFilename(), 
			                    node.getLineNum(),
			                    "the righthand type '" + rightType + 
			                    "' does not conform to the lefthand type '" +
			                    leftType + "' in assignment");
					}
				}
			}
			type = (String) findVariable(node.getName(), ctnode.getParent());
    		if(vt.getScopeLevel(node.getName()) == 1 
    				|| (vt.getScopeLevel(node.getName()) == -1 && type != null)) {
    			errorhandler.register(errorhandler.SEMANT_ERROR,
    					ctnode.getASTNode().getFilename(), 
                        node.getLineNum(),
    					"bad reference '" + node.getName() + 
    					"': fields are 'protected' and can only be " +
    					"accessed within the class or subclass via " +
    					"'this' or 'super'");
    		}
		} else if(node.getRefName().equals("this")) {
			type = (String) findVariable(node.getName(), ctnode);
		} else if(node.getRefName().equals("super")) {
			type = (String) findVariable(node.getName(), ctnode.getParent());
		}
		
		if(type == null) {
			errorhandler.register(errorhandler.SEMANT_ERROR,
					ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
					"variable '" + node.getName() +
					"' in assignment is undeclared");
    	}
		return (type == null) ? "Object" : type; 
    }

    /** Visit a binary comparison equals expression node
      * @param node the binary comparison equals expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryCompEqExpr node) { 
    	String left = (String) node.getLeftExpr().accept(this);
		String right = (String) node.getRightExpr().accept(this);
		
		if(!left.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the lefthand type '"+ left +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: int");
		}
		
		if(!right.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the righthand type '"+ right +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: int");
		}
		
		return "boolean";  
    }

    /** Visit a binary comparison not equals expression node
      * @param node the binary comparison not equals expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryCompNeExpr node) { 
    	String left = (String) node.getLeftExpr().accept(this);
		String right = (String) node.getRightExpr().accept(this);
		
		if(!left.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the lefthand type '"+ left +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: int");
		}
		
		if(!right.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the righthand type '"+ right +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: int");
		}
		
		return "boolean";  
    }

    /** Visit a binary comparison less than expression node
      * @param node the binary comparison less than expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryCompLtExpr node) { 
    	String left = (String) node.getLeftExpr().accept(this);
		String right = (String) node.getRightExpr().accept(this);
		
		if(!left.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the lefthand type '"+ left +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: int");
		}
		
		if(!right.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the righthand type '"+ right +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: int");
		}
		
		return "boolean";  
    }

    /** Visit a binary comparison less than or equal to expression node
      * @param node the binary comparison less than or equal to expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryCompLeqExpr node) { 
    	String left = (String) node.getLeftExpr().accept(this);
		String right = (String) node.getRightExpr().accept(this);
		
		if(!left.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the lefthand type '"+ left +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: int");
		}
		
		if(!right.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the righthand type '"+ right +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: int");
		}
		
		return "boolean";  
    }

    /** Visit a binary comparison greater than expression node
      * @param node the binary comparison greater than expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryCompGtExpr node) { 
    	String left = (String) node.getLeftExpr().accept(this);
		String right = (String) node.getRightExpr().accept(this);
		
		if(!left.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the lefthand type '"+ left +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: int");
		}
		
		if(!right.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the righthand type '"+ right +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: int");
		}
		
		return "boolean";   
    }

    /** Visit a binary comparison greater than or equal to expression node
      * @param node the binary comparison greater to or equal to expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryCompGeqExpr node) { 
    	String left = (String) node.getLeftExpr().accept(this);
		String right = (String) node.getRightExpr().accept(this);

		if(!left.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the lefthand type '"+ left +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: int");
		}
		
		if(!right.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the righthand type '"+ right +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: int");
		}
		
		return "boolean"; 
    }

    /** Visit a binary arithmetic plus expression node
      * @param node the binary arithmetic plus expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryArithPlusExpr node) { 
    	String left = (String) node.getLeftExpr().accept(this);
		String right = (String) node.getRightExpr().accept(this);
		
		if(!left.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the lefthand type '"+ left +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: int");
		}
		
		if(!right.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the righthand type '"+ right +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: int");
		}
		
		return "int"; 
    }

    /** Visit a binary arithmetic minus expression node
      * @param node the binary arithmetic minus expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryArithMinusExpr node) { 
    	String left = (String) node.getLeftExpr().accept(this);
		String right = (String) node.getRightExpr().accept(this);
		
		if(!left.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the lefthand type '"+ left +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: int");
		}
		
		if(!right.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the righthand type '"+ right +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: int");
		}
		
		return "int"; 
    }

    /** Visit a binary arithmetic times expression node
      * @param node the binary arithmetic times expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryArithTimesExpr node) { 
    	String left = (String) node.getLeftExpr().accept(this);
		String right = (String) node.getRightExpr().accept(this);
		
		if(!left.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the lefthand type '"+ left +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: int");
		}
		
		if(!right.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the righthand type '"+ right +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: int");
		}
		
		return "int"; 
    }

    /** Visit a binary arithmetic divide expression node
      * @param node the binary arithmetic divide expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryArithDivideExpr node) { 
    	String left = (String) node.getLeftExpr().accept(this);
		String right = (String) node.getRightExpr().accept(this);
		
		if(!left.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the lefthand type '"+ left +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: int");
		}
		
		if(!right.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the righthand type '"+ right +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: int");
		}
		return "int"; 
    }

    /** Visit a binary arithmetic modulus expression node
      * @param node the binary arithmetic modulus expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryArithModulusExpr node) { 
    	String left = (String) node.getLeftExpr().accept(this);
		String right = (String) node.getRightExpr().accept(this);
		
		if(!left.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the lefthand type '"+ left +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: int");
		}
		
		if(!right.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the righthand type '"+ right +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: int");
		}
		
		return "int"; 
    }


    /** Visit a binary logical AND expression node
      * @param node the binary logical AND expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryLogicAndExpr node) { 
    	String left = (String) node.getLeftExpr().accept(this);
		String right = (String) node.getRightExpr().accept(this);
			
		if(!left.equals("boolean")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the lefthand type '"+ left +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: boolean");
		}
		
		if(!right.equals("boolean")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the righthand type '"+ right +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: boolean");
		}
		
		return "boolean"; 
    }

    /** Visit a binary logical OR expression node
      * @param node the binary logical OR expression node
      * @return result of the visit 
      * */
    public Object visit(BinaryLogicOrExpr node) { 
		String left = (String) node.getLeftExpr().accept(this);
		String right = (String) node.getRightExpr().accept(this);
		
		if(!left.equals("boolean")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the lefthand type '"+ left +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: boolean");
		}
		
		if(!right.equals("boolean")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the righthand type '"+ right +"' in the "+
                    "binary operation ('"+ node.getOperandType() +
                    "') is incorrect; should have been: boolean");
		}
		
		return "boolean"; 
    }

    /** Visit a unary negation expression node
      * @param node the unary negation expression node
      * @return result of the visit 
      * */
    public Object visit(UnaryNegExpr node) { 
    	validStmt = true;
		String type = (String)node.getExpr().accept(this);
		if(!type.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the expression type '"+ type +"' in the "+
                    "unary operation ('-') is incorrect; "+
                    "should have been: int");
		}
		return "int"; 
    }

    /** Visit a unary NOT expression node
      * @param node the unary NOT expression node
      * @return result of the visit 
      * */
    public Object visit(UnaryNotExpr node) { 
    	validStmt = true;
		String type = (String)node.getExpr().accept(this);
		if(!type.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
                    "the expression type '"+ type +"' in the "+
                    "unary operation ('!') is incorrect; "+
                    "should have been: boolean");
		}
		return "int"; 
    }

    /** Visit a unary increment expression node
      * @param node the unary increment expression node
      * @return result of the visit 
      * */
    public Object visit(UnaryIncrExpr node) { 
    	validStmt = true;
		String type = (String)node.getExpr().accept(this);
		if(!type.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
					"the expression type '" + type + "' in the "+
					"unary operation ('++') is incorrect; "+
					"should have been: int");
		}
		return "int"; 
    }

    /** Visit a unary decrement expression node
      * @param node the unary decrement expression node
      * @return result of the visit 
      * */
    public Object visit(UnaryDecrExpr node) { 
    	validStmt = true;
		String type = (String)node.getExpr().accept(this);
		if(!type.equals("int")) {
			errorhandler.register(errorhandler.SEMANT_ERROR, 
                    ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
					"the expression type '" + type + "' in the "+
					"unary operation ('--') is incorrect; "+
					"should have been: int");
		}
		return "int"; 
    }

    /** Visit a variable expression node
      * @param node the variable expression node
      * @return result of the visit 
      * */
    public Object visit(VarExpr node) {
    	
    	System.out.println("Variable " + node.getName() + " :" +
    			(String) findVariable(node.getName(), ctnode) +": " + vt.getSize());
    	
    	privateLength = false;
    	String type = null;
    	
    	if(node.getRef() == null) {
    		if(node.getName().equals("super") || node.getName().equals("this")) {
    			type = node.getName();
    		} else {
	    		type = (String) findVariable(node.getName(), ctnode.getParent());
	    		if(vt.getScopeLevel(node.getName()) == 1 
	    				|| (vt.getScopeLevel(node.getName()) == -1 && type != null)) {
	    			errorhandler.register(errorhandler.SEMANT_ERROR,
	    					ctnode.getASTNode().getFilename(), 
	                        node.getLineNum(),
	    					"bad reference '" + node.getName() + 
	    					"': fields are 'protected' and can only be " +
	    					"accessed within the class or subclass via " +
	    					"'this' or 'super'");
	    		}
	    		if(vt.getScopeLevel(node.getName()) != -1)
	    			type = (String) findVariable(node.getName(), ctnode);
    		}
    	} else if(node.getName().equals("length")) {
    			privateLength = true;
    			type = "int";
    	} else if(node.getRef().equals("this")) {
    		type = (String) findVariable(node.getName(), ctnode);
    	} else if(node.getRef().equals("super")) {
    		type = (String) findVariable(node.getName(), ctnode.getParent());
    	}
    	if(type == null) {
			errorhandler.register(errorhandler.SEMANT_ERROR,
					ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
					"variable '" + node.getName() +
					"' in assignment is undeclared");
    	}
		return (type == null) ? "Object" : type; 
    }

    /** Visit an array expression node
      * @param node the array expression node
      * @return result of the visit 
      * */
    public Object visit(ArrayExpr node) { 
		if (node.getRef() != null)
		    node.getRef().accept(this);
		String type = (String) node.getIndex().accept(this);
		if(!type.equals("int")) {
			//throw error
			errorhandler.register(errorhandler.SEMANT_ERROR,
					ctnode.getASTNode().getFilename(), 
                    node.getLineNum(),
					"bad array index");
		}
		type = (String) vt.lookup(node.getName());
		return (type == null) ? "Object" : type;
    }

    /** Visit an int constant expression node
      * @param node the int constant expression node
      * @return result of the visit 
      * */
    public Object visit(ConstIntExpr node) { 
    	return "int"; 
    }

    /** Visit a boolean constant expression node
      * @param node the boolean constant expression node
      * @return result of the visit 
      * */
    public Object visit(ConstBooleanExpr node) { 
    	return "boolean"; 
    }

    /** Visit a string constant expression node
      * @param node the string constant expression node
      * @return result of the visit 
      * */
    public Object visit(ConstStringExpr node) { 
    	return "String";
    }
    
    boolean isPrimitive(String type) {
    	return (type.equals("int") 
    			|| type.equals("boolean")) ? true : false;
    }
    boolean isObject(String type) {
    	return (getClassTreeNode(type) == null) ? false : true;
    }
    
    public boolean typeConformChecker(String higher, String lower)	{
    	if(higher.equals(lower)) {
    		return true;
    	} else {
    		boolean conformable = false;
    		if(higher.equals("Object") 
    				&& (isObject(lower.replace("[]",""))
    						|| (isPrimitive(lower.replace("[]","")) 
    								&& lower.contains("[]")))) {
    			conformable = true;
    		} else if(isObject(higher) && isObject(lower)) {
    			ClassTreeNode pnode = getClassTreeNode(lower);
    			pnode = pnode.getParent();
    			while(pnode != null) {
    				if(pnode.getName().equals(higher)) {
    					conformable = true;
    					break;
    				}
    				pnode = pnode.getParent();
    			}
    		} else if(lower.contains("[]") && higher.contains("[]")) {
    			higher = higher.replace("[]","");
    			lower = lower.replace("[]","");
    			if(isObject(higher) && isObject(lower)) {
        			ClassTreeNode pnode = getClassTreeNode(lower);
        			pnode = pnode.getParent();
        			while(pnode != null) {
        				if(pnode.getName().equals(higher)) {
        					conformable = true;
        					break;
        				}
        				pnode = pnode.getParent();
        			}
    			}
    		}
    		return conformable;
    	}
    }
    		
    ClassTreeNode getClassTreeNode(String object) {
    	return objectNode.lookupClass(object);
    }
    
    public Object findVariable(String name, ClassTreeNode cnode) {
    	String type = null;
		while(cnode != null) {
			type = (String) cnode.getVarSymbolTable()
				.lookup(name);
			if(type != null) {
				break;
			}
			cnode = cnode.getParent();
		}
    	return type;
    }
    public Object findMethod(String name, ClassTreeNode cnode) {
    	String type = null;
		while(cnode != null) {
				type = (String) cnode.getMethodSymbolTable()
					.lookup(name);
				if(type != null) {
				break;
			}
			cnode = cnode.getParent();
		}
    	return type;
    }
}
